package ffte.core.mixed

import spinal.core._
import spinal.lib._

import ffte.types.VecComplex
import ffte.property.{getFFTIW,getFFTOW,FFTIOConfig}
import ffte.algorithm.FFTGen.{ffter,Method,genericFT,psbFFT,msbFFT}
import ffte.algorithm.{FFTGen,Prime}
import ffte.core.streamed.{Streamed,streamedComponents,sdft2}
import ffte.core.flat.Flat
import ffte.core.{getStreamedGroup}

/**
 * Mixed FFT implementation factory object
 *
 * This object provides factory methods for creating mixed-radix FFT implementations
 * that optimally combine flat and streamed architectures. The mixed approach
 * adapts the implementation strategy to achieve optimal balance between
 * resource usage and performance characteristics.
 *
 * Design Philosophy:
 * The mixed approach recognizes that different stages of an FFT decomposition
 * have different computational requirements and benefit from different
 * implementation strategies:
 * - Small transforms: Benefit from flat (combinatorial) architecture
 * - Large transforms: Benefit from streamed (pipelined) architecture
 * - Mixed approach: Uses optimal architecture for each stage
 *
 * Performance Optimization:
 * - Reduced DSP usage compared to fully streamed implementations
 * - Better timing closure than fully flat implementations
 * - Flexible trade-offs between latency and throughput
 * - Improved resource utilization through architectural diversity
 *
 * Factor Selection Strategy:
 * - Uses streamed group size as primary factor for first stage
 * - Ensures divisibility constraints are met
 * - Chooses PSB vs MSB decomposition based on gcd properties
 */
object Mixed {
    /**
     * Choose optimal factorization for mixed FFT
     *
     * Selects the factorization strategy for mixed FFT implementations
     * based on the global streamed group configuration. The streamed group
     * size is used as one factor to optimize streaming performance.
     *
     * Factor Selection Logic:
     * - Primary factor (i): Streamed group size G
     * - Secondary factor (o): N/G (remaining quotient)
     * - Constraint: N must be divisible by G for proper grouping
     *
     * Validation:
     * Ensures that the transform size N is divisible by the streamed
     * group size G, which is required for proper operation of the
     * mixed architecture.
     *
     * @param N Transform size to factorize
     * @return Tuple of (primary_factor, secondary_factor)
     * @throws AssertionError if N is not divisible by G
     */
    def choose_factor(N: Int) = {
        val G = getStreamedGroup()
        assert(N % G == 0, s"$G%$N must be 0, in mix gen")
        (G, N / G)
    }

    /**
     * Generate standard mixed FFT decomposition
     *
     * Creates a mixed FFT decomposition where the first stage uses a flat
     * implementation and the second stage uses a streamed implementation.
     * This configuration optimizes for reduced resource usage while
     * maintaining good throughput characteristics.
     *
     * Architecture Strategy:
     * - First stage: Flat implementation (combinatorial, low latency)
     * - Second stage: Streamed implementation (pipelined, high throughput)
     * - Factor ordering: (i,o) from choose_factor()
     *
     * Decomposition Selection:
     * - PSB (Prime-Space Block): If factors are coprime (gcd = 1)
     * - MSB (Mixed-Space Block): If factors share common divisors
     *
     * @param N Transform size to generate
     * @return FFT decomposition structure with mixed architecture
     */
    def gen(N: Int): ffter[Method] = {
        val (i, o) = choose_factor(N)
        val ib = FFTGen.gen[Flat](i).asInstanceOf[ffter[Method]]
        val ob = FFTGen.gen[Streamed](o).asInstanceOf[ffter[Method]]
        if (Prime.gcd(i, o) == 1) FFTGen.psb(ib, ob, false)
        else FFTGen.msb(ib, ob, false)
    }

    /**
     * Generate reverse mixed FFT decomposition
     *
     * Creates a mixed FFT decomposition where the first stage uses a streamed
     * implementation and the second stage uses a flat implementation. This
     * configuration optimizes for high throughput with reduced intermediate
     * storage requirements.
     *
     * Architecture Strategy:
     * - First stage: Streamed implementation (pipelined, high throughput)
     * - Second stage: Flat implementation (combinatorial, low latency)
     * - Factor ordering: (o,i) - swapped from standard version
     *
     * Decomposition Selection:
     * - PSB (Prime-Space Block): If factors are coprime (gcd = 1)
     * - MSB (Mixed-Space Block): If factors share common divisors
     *
     * @param N Transform size to generate
     * @return FFT decomposition structure with reverse mixed architecture
     */
    def rgen(N: Int): ffter[Method] = {
        val (o, i) = choose_factor(N)
        val ib = FFTGen.rgen[Streamed](i).asInstanceOf[ffter[Method]]
        val ob = FFTGen.rgen[Flat](o).asInstanceOf[ffter[Method]]
        if (Prime.gcd(i, o) == 1) FFTGen.psb(ib, ob, true)
        else FFTGen.msb(ib, ob, true)
    }
}

/**
 * Mixed-radix FFT implementation method
 *
 * This class provides the core implementation method for generating mixed-radix
 * FFT hardware components that combine flat and streamed architectures. The mixed
 * approach optimizes resource usage while maintaining good performance characteristics
 * by adapting the implementation strategy to each stage's requirements.
 *
 * Architecture Characteristics:
 * - Hybrid design: Combines flat and streamed implementations
 * - Adaptive optimization: Selects optimal architecture per stage
 * - Resource efficiency: Reduced DSP usage compared to fully streamed
 * - Performance balance: Better latency than fully streamed implementations
 *
 * Algorithm Support:
 * - psbFFT[Mixed]: Prime-space decomposition with mixed architecture
 * - msbFFT[Mixed]: Mixed-space decomposition with mixed architecture
 * - Fallback: Simple 2-point streamed DFT for unsupported cases
 *
 * Scaling Strategy:
 * - Uses configurable scaling for fixed-point arithmetic
 * - Scaling distributed across both implementation domains
 * - Precision management across architecture boundaries
 *
 * @param shift Scaling shift amount for fixed-point overflow prevention
 */
class Mixed(shift: Int) extends Method {
    /**
     * Generate mixed FFT component from algorithm specification
     *
     * This method serves as the factory for creating concrete mixed FFT hardware
     * components from abstract algorithm specifications. It uses pattern matching
     * to select the appropriate mixed implementation based on the FFT algorithm type.
     *
     * Implementation Selection Logic:
     * - psbFFT[Mixed]: Prime-space decomposition with mixed architecture
     * - msbFFT[Mixed]: Mixed-space decomposition with mixed architecture
     * - Default: 2-point streamed DFT for simple cases or fallback
     *
     * The mixed implementations combine flat and streamed architectures
     * according to the decomposition requirements to achieve optimal
     * resource usage and performance characteristics.
     *
     * @param entity Abstract FFT algorithm specification
     * @param prefix Naming prefix for generated components (for debugging/identification)
     * @return Concrete mixed FFT component with streaming interface
     */
    def apply(entity: genericFT, prefix: String): streamedComponents = {
        entity match {
            case d: psbFFT[Mixed] =>
                // Prime-space decomposition with mixed architecture
                // Uses Chinese Remainder Theorem with adaptive flat/streamed stages
                new psb(shift, d)
            case d: msbFFT[Mixed] =>
                // Mixed-space decomposition with mixed architecture
                // Uses Cooley-Tukey algorithm with adaptive flat/streamed stages
                new msb(shift, d)
            case _ =>
                // Fallback to simple 2-point streamed DFT
                // Used for basic transforms or unsupported algorithm types
                new sdft2(shift)
        }
    }
}